package io.github.xxyopen.novel.core.aspect;

import io.github.xxyopen.novel.core.annotation.Key;
import io.github.xxyopen.novel.core.annotation.Lock;
import io.github.xxyopen.novel.core.common.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁切面（基于Redisson实现）
 * 功能：通过AOP拦截@Lock注解方法，实现分布式环境下的方法级锁控制
 * 核心机制：
 * 1. 根据方法参数动态生成分布式锁Key
 * 2. 支持阻塞/非阻塞两种加锁模式
 * 3. 自动释放锁资源，防止死锁
 */
@Aspect
@Component
@RequiredArgsConstructor
public class LockAspect {

    private final RedissonClient redissonClient; // Redisson客户端实例

    // 锁Key前缀与分隔符定义
    private static final String KEY_PREFIX = "Lock";
    private static final String  KEY_SEPARATOR = "::";

    /**
     * 环绕增强处理（核心锁逻辑）
     * @param joinPoint 被拦截的方法连接点
     * @return 原方法返回值
     * @throws BusinessException 获取锁失败时抛出指定错误码异常
     */
    @Around(value = "@annotation(io.github.xxyopen.novel.core.annotation.Lock)")
    @SneakyThrows
    public Object doAround(ProceedingJoinPoint joinPoint) {
        // 获取方法元数据
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method targetMethod = methodSignature.getMethod();

        // 提取@Lock注解配置
        Lock lock = targetMethod.getAnnotation(Lock.class);

        // 构建分布式锁Key（格式示例：Lock::prefix::param1::param2）
        String lockKey = KEY_PREFIX + buildLockKey(lock.prefix(), targetMethod, joinPoint.getArgs());

        // 获取Redisson分布式锁实例
        RLock rLock = redissonClient.getLock(lockKey);

        // 根据配置选择锁获取方式
        boolean lockAcquired = lock.isWait() ?
                rLock.tryLock(lock.waitTime(), TimeUnit.SECONDS) : // 带等待时间的尝试获取
                rLock.tryLock();                                   // 立即尝试获取

        if (lockAcquired) {
            try {
                // 执行被拦截方法
                return joinPoint.proceed();
            } finally {
                // 确保锁释放
                rLock.unlock();
            }
        }
        // 获取锁失败时抛出业务异常
        throw new BusinessException(lock.failCode());
    }

    /**
     * 构建锁Key（非公开方法）
     * 逻辑：组合前缀+被@Key标注的参数值
     * @param prefix 锁前缀（来自注解配置）
     * @param method 目标方法
     * @param args 方法参数数组
     * @return 拼接后的锁标识后缀
     */
    private String buildLockKey(String prefix, Method method, Object[] args) {
        StringBuilder builder = new StringBuilder();
        // 添加前缀部分
        if (StringUtils.hasText(prefix)) {
            builder.append(KEY_SEPARATOR).append(prefix);
        }
        // 遍历方法参数
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            builder.append(KEY_SEPARATOR);
            // 仅处理带@Key注解的参数
            if (parameters[i].isAnnotationPresent(Key.class)) {
                Key key = parameters[i].getAnnotation(Key.class);
                // 解析参数值（支持SpEL表达式）
                builder.append(parseKeyExpr(key.expr(), args[i]));
            }
        }
        return builder.toString();
    }

    /**
     * 解析参数键值表达式（私有方法）
     * @param expr SpEL表达式（可为空）
     * @param arg 参数实例
     * @return 表达式解析结果或直接toString()
     */
    private String parseKeyExpr(String expr, Object arg) {
        // 无表达式直接返回字符串形式
        if (!StringUtils.hasText(expr)) {
            return arg.toString();
        }
        // 使用SpEL解析表达式
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(expr, new TemplateParserContext());
        return expression.getValue(arg, String.class);
    }

}




/*package io.github.xxyopen.novel.core.aspect;

        import io.github.xxyopen.novel.core.annotation.Key;
        import io.github.xxyopen.novel.core.annotation.Lock;
        import io.github.xxyopen.novel.core.common.exception.BusinessException;
        import lombok.RequiredArgsConstructor;
        import lombok.SneakyThrows;
        import org.aspectj.lang.ProceedingJoinPoint;
        import org.aspectj.lang.annotation.Around;
        import org.aspectj.lang.annotation.Aspect;
        import org.aspectj.lang.reflect.MethodSignature;
        import org.redisson.api.RLock;
        import org.redisson.api.RedissonClient;
        import org.springframework.expression.Expression;
        import org.springframework.expression.ExpressionParser;
        import org.springframework.expression.common.TemplateParserContext;
        import org.springframework.expression.spel.standard.SpelExpressionParser;
        import org.springframework.stereotype.Component;
        import org.springframework.util.StringUtils;

        import java.lang.reflect.Method;
        import java.lang.reflect.Parameter;
        import java.util.concurrent.TimeUnit;

    private static final String KEY_SEPARATOR = "::";

    @Around(value = "@annotation(io.github.xxyopen.novel.core.annotation.Lock)")
    @SneakyThrows
    public Object doAround(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method targetMethod = methodSignature.getMethod();
        Lock lock = targetMethod.getAnnotation(Lock.class);
        String lockKey = KEY_PREFIX + buildLockKey(lock.prefix(), targetMethod,
                joinPoint.getArgs());
        RLock rLock = redissonClient.getLock(lockKey);
        if (lock.isWait() ? rLock.tryLock(lock.waitTime(), TimeUnit.SECONDS) : rLock.tryLock()) {
            try {
                return joinPoint.proceed();
            } finally {
                rLock.unlock();
            }
        }
        throw new BusinessException(lock.failCode());
    }

    private String buildLockKey(String prefix, Method method, Object[] args) {
        StringBuilder builder = new StringBuilder();
        if (StringUtils.hasText(prefix)) {
            builder.append(KEY_SEPARATOR).append(prefix);
        }
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            builder.append(KEY_SEPARATOR);
            if (parameters[i].isAnnotationPresent(Key.class)) {
                Key key = parameters[i].getAnnotation(Key.class);
                builder.append(parseKeyExpr(key.expr(), args[i]));
            }
        }
        return builder.toString();
    }

    private String parseKeyExpr(String expr, Object arg) {
        if (!StringUtils.hasText(expr)) {
            return arg.toString();
        }
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(expr, new TemplateParserContext());
        return expression.getValue(arg, String.class);
    }

}*/